<?php
declare(strict_types=1);

namespace SnowFlake\Engine;

use Contract\Exceptions\ValidationException;
use SnowFlake\Util\MathUtil;

class SnowFlakeEngine implements SnowFlakeEngineInterface
{
    protected array $config = [
        'fields' => [],
        'extends' => [
            'verify_code_algorithm' => [
                'seed' => 'hakuna_matata',
                'factors' => [8, 218]
            ]
        ],
    ];

    /** @var array $params */
    protected array $params;

    /**
     * @param array $config
     */
    public function setConfig(array $config): void
    {
        foreach ($config as $key => $value) {
            $this->config[$key] = $value;
        }
    }

    /**
     * @param string $keyString
     * @return array|mixed
     * @throws ValidationException
     */
    public function getConfig(string $keyString)
    {
        $keys = explode('.', $keyString);
        $config = $this->config;
        foreach ($keys as $key) {
            if (empty($key)) {
                throw new ValidationException('key不合法, 存在空键值');
            }
            if (!array_key_exists($key, $config)) {
                throw new ValidationException('config中不存在键值, keyString:' . $keyString);
            }
            $config = $config[$key];
        }
        return $config;
    }

    /**
     * @param array $params
     * @return void
     * @throws ValidationException
     */
    public function setParams(array $params): void
    {
        foreach ($this->getConfig('fields') as $field => $config) {
            if ($field != 'verify_code') {
                if (empty($params[$field])) {
                    throw new ValidationException(sprintf('params 中缺少%s字段', $field));
                }
                if (!is_numeric($params[$field])) {
                    throw new ValidationException(sprintf('params.%s 必须是数值', $field));
                }
                $this->params[$field] = $this->parseField($params[$field], $config['length']);
            }
        }
    }

    /**
     * @return int
     * @throws ValidationException
     */
    public function id(): int
    {
        $this->validateConfig();
        $id = 0;
        $fieldLengths = $this->getFieldLengths();
        $totalLength = array_sum($fieldLengths);
        foreach ($fieldLengths as $field => $length) {
            $value = $field != 'verify_code' ? $this->params[$field] : $this->getVerifyCode($this->params);
            $id = $id | ($value << $totalLength - $length);
            $totalLength = $totalLength - $length;
        }
        return $id;
    }

    /**
     * @param int $id
     * @return bool
     * @throws ValidationException
     */
    public function verify(int $id): bool
    {
        $this->validateConfig();
        $fieldLengths = $this->getFieldLengths();
        $fieldLengths = array_reverse($fieldLengths);
        $verifyCode = 0;
        foreach ($fieldLengths as $field => $length) {
            if ($field != 'verify_code') {
                $this->params[$field] = MathUtil::getBitMod($id, $length);
            } else {
                $verifyCode = MathUtil::getBitMod($id, $length);
            }
            $id = $id >> $length;
        }
        return $this->getVerifyCode($this->params) === $verifyCode;
    }

    /**
     * @param $value
     * @param $length
     * @return int
     */
    protected function parseField($value, $length): int
    {
        return MathUtil::getBitMod($value, $length);
    }

    /**
     * @param array $params
     * @return int
     * @throws ValidationException
     */
    protected function getVerifyCode(array $params): int
    {
        $verifyCodeAlgorithm = $this->getConfig('extends.verify_code_algorithm');
        $originalString = sprintf('%s%s', array_sum($params) * $verifyCodeAlgorithm['factors'][0] + $verifyCodeAlgorithm['factors'][1], $verifyCodeAlgorithm['seed']);
        $number = crc32($originalString);
        return MathUtil::getBitMod($number, $this->getConfig('fields.verify_code.length'));
    }

    /**
     * @throws ValidationException
     */
    protected function validateConfig(): void
    {
        if (empty($this->config['fields'])) {
            throw new ValidationException('fields 不能为空');
        }
        foreach ($this->config['fields'] as $field => $config) {
            if (empty($config['length'])) {
                throw new ValidationException(sprintf('fields.%s.length 不能为空', $field));
            }
            if (!is_numeric($config['length']) || $config['length'] <= 0) {
                throw new ValidationException(sprintf('fields.%s.length 必须为正正数', $field));
            }
        }
        if (!empty($this->config['fields']['verify_code'])) {
            if (empty($this->config['extends']['verify_code_algorithm'])) {
                throw new ValidationException('extends.verify_code_algorithm 不能为空');
            }
            if (empty($this->config['extends']['verify_code_algorithm']['seed'])) {
                throw new ValidationException('extends.verify_code_algorithm.seed 不能为空');
            }
            if (empty($this->config['extends']['verify_code_algorithm']['factors'])) {
                throw new ValidationException('extends.verify_code_algorithm.factors 不能为空');
            }
            if (!is_array($this->config['extends']['verify_code_algorithm']['factors']) || count($this->config['extends']['verify_code_algorithm']['factors']) != 2) {
                throw new ValidationException('extends.verify_code_algorithm.factors 必须是数组, 且长度为2');
            }
        }
        $fieldLengths = $this->getFieldLengths();
        if (array_sum($fieldLengths) > 64) {
            throw new ValidationException('id 总长度不能超过64位');
        }
    }

    /**
     * @return void
     * @throws ValidationException
     */
    protected function validateVerify(): void
    {
        if (!empty($this->config['fields']['verify_code']['length'])) {
            throw new ValidationException('fields.verify_code.length 为空，不存在校验码');
        }
    }

    /**
     * @return array
     * @throws ValidationException
     */
    protected function getFieldLengths(): array
    {
        $lengths = [];
        foreach ($this->getConfig('fields') as $field => $config) {
            $lengths[$field] = $config['length'];
        }
        return $lengths;
    }


}